/****************************************************************************
* Copyright (c) 2008, j. montgomery, ATGi                                   *
* All rights reserved.                                                      *
*                                                                           *
* Redistribution and use in source and binary forms, with or without        *
* modification, are permitted provided that the following conditions        *
* are met:                                                                  *
*                                                                           *
* - Redistributions of source code must retain the above copyright          *
*   notice, this list of conditions and the following disclaimer.           *
*                                                                           *
* - Redistributions in binary form must reproduce the above                 *
*   copyright notice, this list of conditions and the following             *
*   disclaimer in the documentation and/or other materials provided         *
*   with the distribution.                                                  *
*                                                                           *
* - Neither the name of ATGi nor the names of its contributors may          *
*   be used to endorse or promote products derived from this software       *
*   without specific prior written permission.                              *
*                                                                           *
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS       *
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT         *
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS         *
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE            *
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,       *
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES                  *
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR        *
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)        *
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,       *
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)             *
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED       *
* OF THE POSSIBILITY OF SUCH DAMAGE.                                        *
*****************************************************************************
*/
using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;

namespace DnSmtp.Message.Encoding
{
	/// <summary>
	/// implementation of Quoted Printable
	/// Multipart Internet Mail Encoding (MIME) which encodes every 
	/// character, not just "special characters" for transmission over 
	/// SMTP.
	/// </summary>
	/// <para>
	/// More information on the quoted-printable encoding can be found
	/// here: http://www.freesoft.org/CIE/RFC/1521/6.htm
	/// </para>
 	/// <para>
	/// detailed in: RFC 1521
	/// </para>
	/// <para>
	/// more info: http://www.freesoft.org/CIE/RFC/1521/6.htm
	/// </para>
	public class QuotedPrintableEncodingStrategy : IEncodingStrategy
	{
		private static QuotedPrintableEncodingStrategy _instance;
		protected int MAX_LINE_CHAR_LENGTH=75;

		public static QuotedPrintableEncodingStrategy Instance
		{
			get
			{
				if(_instance == null) 
				{
					_instance = new QuotedPrintableEncodingStrategy();
				}
				return _instance;
			}
		}

		public QuotedPrintableEncodingStrategy()
		{

		}

		#region IEncodingStrategy Members

		public string Name
		{
			get
			{
				return "quoted-printable";
			}
		}

		public string Encode(object message)
		{
			string result = string.Empty;
			
			if(message is Stream) 
			{
				byte[] bytes = new byte[((Stream)message).Length];
				((Stream)message).Read(bytes, 0, (int) ((Stream)message).Length);
				result = this.Encode(ASCIIEncoding.ASCII.GetString(bytes), MAX_LINE_CHAR_LENGTH);
			}
			else if(message is string) 
			{
				result = this.Encode((string)message, MAX_LINE_CHAR_LENGTH);
			}
			else 
			{
				throw new NotImplementedException("Unable to encode message of type " + message.GetType().FullName);
			}

			return result;
		}
		#endregion
		
		public string Encode(string toencode, int charsperline)
		{
			if (toencode == null)
				throw new ArgumentNullException();

			if (charsperline <= 0)
				throw new ArgumentOutOfRangeException();

			string line, encodedHtml = "";
			StringReader sr = new StringReader(toencode);
			try
			{
				while((line=sr.ReadLine())!=null)
					encodedHtml += EncodeSmallLine(line);

				return this.FormatStringWidth(encodedHtml, charsperline);
			}
			finally
			{
				sr.Close();
			}
		}

		public static string EncodeSmallLine(string s)
		{
			if (s == null)
				throw new ArgumentNullException();

			return EncodeSmall(s + "\r\n");
		}

		public static string EncodeSmall(string s)
		{
			if (s == null)
				throw new ArgumentNullException();

			StringBuilder sb  = new StringBuilder();
			
			foreach(char c in s)
			{
				int code = Convert.ToInt32(c);
				sb.Append(String.Format("={0}", code.ToString("X2")));
			}
		
			return sb.ToString();
		}

		
		public string EncodeFile(string filepath)
		{
			return EncodeFile(filepath, MAX_LINE_CHAR_LENGTH);
		}

		public string EncodeFile(string filepath, int charsperline)
		{
			if (filepath == null)
				throw new ArgumentNullException();

			string encodedHtml = "", line;
			FileInfo f = new FileInfo(filepath);
			
			if(!f.Exists)
				throw new FileNotFoundException();

			StreamReader sr = f.OpenText();
			try
			{
				while((line=sr.ReadLine())!=null)
					encodedHtml += EncodeSmallLine(line);

				return this.FormatStringWidth(encodedHtml, charsperline);
			}
			finally
			{
				sr.Close();
			}
		}

		public string Decode(string encoded)
		{
			if (encoded == null)
				throw new ArgumentNullException();

			string line;
			StringWriter sw = new StringWriter();
			StringReader sr = new StringReader(encoded);
			try
			{
				while((line=sr.ReadLine())!=null)
				{
					if (line.EndsWith("="))
						sw.Write(HexDecoder(line.Substring(0, line.Length-1)));
					else
						sw.WriteLine(HexDecoder(line));

					sw.Flush();
				}
				return sw.ToString();
			}
			finally
			{
				sw.Close();
				sr.Close();
			}
		}

		public string HexDecoder(string line)
		{
			if (line == null)
				throw new ArgumentNullException();

			//parse looking for =XX where XX is hexadecimal
			Regex re = new Regex(
				"(\\=([0-9A-F][0-9A-F]))", 
				RegexOptions.IgnoreCase
				);
			return re.Replace(line, new MatchEvaluator(HexDecoderEvaluator));
		}

		private string HexDecoderEvaluator(Match m)
		{
			string hex = m.Groups[2].Value;
			int iHex = Convert.ToInt32(hex, 16);
			char c = (char) iHex;
			return c.ToString();
		}

		private string FormatStringWidth(string s, int maxcharlen)
		{
			if (s == null)
				throw new ArgumentNullException();

			StringBuilder sb = new StringBuilder();
			StringWriter sw = new StringWriter();

			try
			{
				int i=0;
				
				foreach(char c in s) 
				{
					sb.Append(c.ToString());
					i++;
					if (i==maxcharlen)
					{
						sw.Write("{0}={1}", sb.ToString(), MailMessage.CRLF);
						sw.Flush();

						i=0;
						sb = new StringBuilder();
					}
				}
				
				sw.Write(sb.ToString() + MailMessage.CRLF);
				sw.Flush();

				return sw.ToString();
			}
			finally
			{
				sw.Close();
			}
		}
	}
}
